package at.fhj.antesk.steamvaluecalc.implementations;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import at.fhj.antesk.steamvaluecalc.data.Game;
import at.fhj.antesk.steamvaluecalc.data.Price;
import at.fhj.antesk.steamvaluecalc.data.Profile;
import at.fhj.antesk.steamvaluecalc.enums.Currency;
import at.fhj.antesk.steamvaluecalc.enums.PrivacyState;
import at.fhj.antesk.steamvaluecalc.enums.VisibilityState;
import at.fhj.antesk.steamvaluecalc.exceptions.DataRetrieverException;
import at.fhj.antesk.steamvaluecalc.interfaces.DataRetriever;
import at.fhj.antesk.steamvaluecalc.utils.UrlBuilder;


public class DataRetrieverImpl implements DataRetriever {

    private static final String PRICE = "price";
    private static final String DISCOUNT_FINAL_PRICE = "discount_final_price";
    private static final String APPHUB_STORE_PRICE = "apphub_StorePrice";
    private static Logger logger = LoggerFactory.getLogger(DataRetrieverImpl.class);
    private static final String LINE_SEPERATOR = System.getProperty("line.separator");
    private static final int MAX_RETRIES = 5;

    private DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

    public String retrieveData(String url) {
        StringBuilder contents = new StringBuilder();
        URL urlToConnect = null;
        try {
            urlToConnect = new URL(url);
        } catch (MalformedURLException e) {
            logger.error("cannot create url: " + url, e);
            return null;
        }

        BufferedReader input = null;

        try {
            InputStreamReader inputStream = new InputStreamReader(urlToConnect.openStream());
            input = new BufferedReader(inputStream);

            while (input.ready()) {
                String line = input.readLine();
                contents.append(line);
                if (input.ready()) {
                    contents.append(LINE_SEPERATOR);
                }
            }

            input.close();
        } catch (IOException ex) {
            logger.error("cannot retrieve data from url: " + url, ex);
        }

        return contents.toString();
    }

    public Profile retrieveProfile(String url) {
        Profile profile = new Profile();

        for (int rc = 1; rc <= MAX_RETRIES; rc++) {
            try {
                DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
                Document document = documentBuilder.parse(url);
                NodeList profileDataList = document.getElementsByTagName("profile").item(0).getChildNodes();

                for (int i = 0; i < profileDataList.getLength(); i++) {
                    String nodeName = profileDataList.item(i).getNodeName();
                    String textContent = profileDataList.item(i).getTextContent();

                    if ("steamID64".equals(nodeName)) {
                        profile.setProfileId(textContent);
                        continue;
                    } else if ("steamID".equals(nodeName)) {
                        profile.setName(textContent);
                        continue;
                    } else if ("privacyState".equals(nodeName)) {
                        PrivacyState privacyState = assignPrivacyState(textContent);
                        profile.setPrivacyState(privacyState);
                        continue;
                    } else if ("visibilityState".equals(nodeName)) {
                        VisibilityState visibilityState = assignVisibilityState(textContent);
                        profile.setVisibilityState(visibilityState);
                        continue;
                    } else if ("avatarIcon".equals(nodeName)) {
                        profile.setAvatarIconUrl(textContent);
                        continue;
                    } else if ("customURL".equals(nodeName)) {
                        profile.setCustomUrl(textContent);
                        continue;
                    } else if ("location".equals(nodeName)) {
                        if (!textContent.isEmpty()) {
                            profile.setLocation(textContent);
                        }
                        continue;
                    } else if ("realname".equals(nodeName)) {
                        if (!textContent.isEmpty()) {
                            profile.setRealName(textContent);
                        }
                        continue;
                    } else if ("summary".equals(nodeName)) {
                        profile.setSummary(textContent);
                        continue;
                    }

                }

                logger.debug("Successfully retrieved Profile information for " + url);
                break;
            } catch (ParserConfigurationException e) {
                logger.error("Parser error", e);
            } catch (SAXException e) {
                logger.error("Parser error", e);
            } catch (IOException e) {
                logger.error("Steam did not respond after " + rc + ". try. Trying again...");
                continue;
            }
        }

        return profile;
    }

    private VisibilityState assignVisibilityState(String textContent) {
        if (textContent.equals(VisibilityState.ONE.getValue())) {
            return VisibilityState.ONE;
        } else if (textContent.equals(VisibilityState.TWO.getValue())) {
            return VisibilityState.TWO;
        } else if (textContent.equals(VisibilityState.THREE.getValue())) {
            return VisibilityState.THREE;
        }

        logger.error("VisibilityState in XML did not match any known values");
        return null;
    }

    private PrivacyState assignPrivacyState(String textContent) {
        if (textContent.equals(PrivacyState.PUBLIC.getValue())) {
            return PrivacyState.PUBLIC;
        } else if (textContent.equals(PrivacyState.PRIVATE.getValue())) {
            return PrivacyState.PRIVATE;
        } else if (textContent.equals(PrivacyState.FRIENDS.getValue())) {
            return PrivacyState.FRIENDS;
        } else if (textContent.equals(PrivacyState.FRIENDS_OF_FRIENDS.getValue())) {
            return PrivacyState.FRIENDS_OF_FRIENDS;
        } else if (textContent.equals(PrivacyState.USERS_ONLY.getValue())) {
            return PrivacyState.USERS_ONLY;
        }

        logger.error("PrivacyState in XML did not match any known values");
        return null;
    }

    public List<Game> retrieveGameList(String url) {
        List<Game> gameList = new ArrayList<Game>();

        for (int rc = 1; rc <= MAX_RETRIES; rc++) {
            try {
                DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
                Document document = documentBuilder.parse(url);
                NodeList games = document.getElementsByTagName("games").item(0).getChildNodes();

                for (int i = 0; i < games.getLength(); i++) {
                    NodeList currentGameData = games.item(i).getChildNodes();
                    Game game = new Game();
                    for (int j = 0; j < currentGameData.getLength(); j++) {
                        String nodeName = currentGameData.item(j).getNodeName();
                        String textContent = currentGameData.item(j).getTextContent();
                        if (!"#text".equals(nodeName)) {
                            if ("appID".equals(nodeName)) {
                                game.setId(textContent);
                                continue;
                            } else if ("name".equals(nodeName)) {
                                game.setName(textContent);
                                continue;
                            } else if ("logo".equals(nodeName)) {
                                game.setLogo(textContent);
                                continue;
                            } else if ("storeLink".equals(nodeName)) {
                                game.setStoreLink(textContent);
                                continue;
                            } else if ("hoursOnRecord".equals(nodeName)) {
                                game.setHoursOnRecord(Double.parseDouble(textContent));
                            }
                        }

                        gameList.add(game);
                    }
                }

                logger.debug("Successfully retrieved game list for " + url);
                break;
            } catch (ParserConfigurationException e) {
                logger.error("Parser error", e);
            } catch (SAXException e) {
                logger.error("Parser error", e);
            } catch (IOException e) {
                logger.error("Steam did not respond after " + rc + ". try. Trying again...");
                continue;
            }
        }

        return gameList;
    }

    public Price retrievePriceForGame(Game game) throws DataRetrieverException {
        Price price = new Price();

        try {
            String url = UrlBuilder.createCommunityUrlForAppId(game.getId());
            Element body = Jsoup.connect(url).get().body();
            Elements allElements = body.getAllElements();

            if (!allElements.hasClass(APPHUB_STORE_PRICE)) {
                return null;
            }

            Elements priceElement = body.getElementsByClass(PRICE);

            if (priceElement.isEmpty()) {
                priceElement = body.getElementsByClass(DISCOUNT_FINAL_PRICE);
                if (priceElement.isEmpty()) {
                    throw new DataRetrieverException("priceElement not found");
                }
            }

            parsePrice(price, priceElement);
        } catch (IOException e) {
            throw new DataRetrieverException(e);
        }

        return price;
    }

    private void parsePrice(Price price, Elements priceElement) {
        String priceTag = priceElement.text().replace(",", ".");

        String currencyString = priceTag.substring(priceTag.length() - 1, priceTag.length());
        String amountString = priceTag.substring(0, priceTag.length() - 1);

        Double amount = Double.parseDouble(amountString);
        price.setAmount(amount);
        price.setCurrency(Currency.INVALID);

        Currency[] currencies = Currency.values();
        for (Currency cur : currencies) {
            if (cur.getValue().equals(currencyString)) {
                price.setCurrency(cur);
                break;
            }
        }
    }

}
